﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using ExpressionEvaluator.Expceptions;
using ExpressionEvaluator.Extended;

namespace ExpressionEvaluator.BuilderTreeNode
{
	/// <summary>
	/// Node containing function or property node expression
	/// </summary>
	internal class FunctionOrPropertyNode : OpaqueExpressionNode
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="FunctionOrPropertyNode"/> class.
		/// </summary>
		public FunctionOrPropertyNode()
		{
			IsProperty = false;
		}

		/// <summary>
		/// Gets or sets a value indicating whether this instance is property.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is property; otherwise, <c>false</c>.
		/// </value>
		public bool IsProperty
		{
			get;
			set;
		}

		/// <summary>
		/// Gets the parent expression for expression.
		/// </summary>
		/// <param name="parent">The parent expression.</param>
		/// <param name="tree">The tree.</param>
		/// <returns>Expression for this node.</returns>
		public override Expression GetParentExpression(Expression parent, BuilderTree tree)
		{
			// if [is property] is true then use property or field expression
			if (IsProperty)
			{
				return Expression.PropertyOrField(parent, Idents[0].Text);
			}
			// else use function expression
			else
			{
				// if any subnode contains lambda expersion use prediction mechanism
				if (Nodes.Any(node => (node is ExpressionNode) && ((node as ExpressionNode).Operator is ExpressionOperatorLambda)))
				{
					List<ParameterForLambdaResolve> predictedParameters = new List<ParameterForLambdaResolve>();
					// compute parameters for find related lambda function
					foreach (BuilderTreeNodeBase node in Nodes)
					{
						if ((node is ExpressionNode) && ((node as ExpressionNode).Operator is ExpressionOperatorLambda))
						{
							predictedParameters.Add(new ParameterForLambdaResolve(node));
						}
						else
						{
							predictedParameters.Add(new ParameterForLambdaResolve(node.GetExpression(tree)));
						}
					}
					return ExtendedResolve.CreateExpression(parent, tree, Idents[0], predictedParameters);
				}

				List<Expression> functionParameters = new List<Expression>();
				List<Type> parameterTypes = new List<Type>();

				foreach (BuilderTreeNodeBase node in Nodes)
				{
					Expression nodeExpression = null;
					nodeExpression = node.GetExpression(tree);
					functionParameters.Add(nodeExpression);
					parameterTypes.Add(nodeExpression.Type);
				}
				// try to find method
				MethodInfo method = parent.Type.GetMethod(Idents[0].Text, parameterTypes.ToArray());
				// if unable to find method, try to find extension method via extended resolver
				if (method == null)
				{
					List<ParameterForLambdaResolve> predictedParameters = new List<ParameterForLambdaResolve>();
					foreach (var param in functionParameters)
					{
						predictedParameters.Add(new ParameterForLambdaResolve(param));
					}
					return ExtendedResolve.CreateExpression(parent, tree, Idents[0], predictedParameters);
				}
				return Expression.Call(parent, method, functionParameters.ToArray());
			}
			
		}

		/// <summary>
		/// Gets the parent expression for type.
		/// </summary>
		/// <param name="parent">The parent type.</param>
		/// <param name="tree">The tree.</param>
		/// <returns>Expression for this node.</returns>
		public override Expression GetParentExpression(Type parent, BuilderTree tree)
		{
			// if [is property] is true then use property or field expression
			if (IsProperty)
			{
				return ExpressionNode.GetPropertyOrFieldExpression(parent, Idents[0].Text);
			}
			// else use function expression
			else
			{
				List<Expression> functionParameters = new List<Expression>();
				List<Type> parameterTypes = new List<Type>();
				foreach (BuilderTreeNodeBase node in Nodes)
				{
					Expression nodeExpression = node.GetExpression(tree);
					functionParameters.Add(nodeExpression);
					parameterTypes.Add(nodeExpression.Type);
				}
				return Expression.Call(parent.GetMethod(Idents[0].Text, parameterTypes.ToArray()), functionParameters.ToArray());
			}
		}
		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString()
		{
			StringBuilder result = new StringBuilder();

			result.Append("." + Idents[0].Text);
			if (Nodes.Count > 0)
			{
				result.Append("(");
				for (int i = 0; i < Nodes.Count; ++i)
				{
					if (i > 0)
					{
						result.Append(", ");
					}
					result.Append(Nodes[i].ToString());
				}
				result.Append(")");
			}
			return result.ToString();
		}


		
	}
}
